package main

import (
    "encoding/base64"
    "fmt"
    "io"
    "log"
    "net"
    "net/http"
    "os"
    "strings"
    "time"
    "github.com/skip2/go-qrcode"
)

const maxUploadSize = 1 << 30 // 1GB

// 判断一个网卡是否是虚拟网卡，包括"virtual"
func isVirtualInterface(name string) bool {
    name = strings.ToLower(name)
    virtualPrefixes := []string{
        "veth", "docker", "vmnet", "vboxnet", "virbr", "npf_", "hyper-v", "tap", "tun", "lo",
        "loopback", "npcap", "br-", "wg", "vmware", "virtual",
    }
    for _, prefix := range virtualPrefixes {
        if strings.HasPrefix(name, prefix) || strings.Contains(name, prefix) {
            return true
        }
    }
    return false
}

// 获取所有可用物理网卡的IPv4地址（只根据网卡名排除虚拟网卡）
func getAllUsableIPs() []string {
    var ips []string
    ifas, err := net.Interfaces()
    if err != nil {
        return []string{"localhost"}
    }
    for _, ifa := range ifas {
        if ifa.Flags&net.FlagUp == 0 || ifa.Flags&net.FlagLoopback != 0 {
            continue
        }
        if isVirtualInterface(ifa.Name) {
            continue
        }
        addrs, err := ifa.Addrs()
        if err != nil {
            continue
        }
        for _, addr := range addrs {
            var ip net.IP
            switch v := addr.(type) {
            case *net.IPNet:
                ip = v.IP
            case *net.IPAddr:
                ip = v.IP
            }
            if ip == nil || ip.IsLoopback() {
                continue
            }
            ip = ip.To4()
            if ip == nil {
                continue
            }
            ips = append(ips, ip.String())
        }
    }
    if len(ips) == 0 {
        return []string{"localhost"}
    }
    return ips
}

var chatMemory []string // 聊天内容仅保存在内存

func main() {
    http.HandleFunc("/", indexHandler)
    http.HandleFunc("/upload", uploadHandler)
    http.HandleFunc("/chat", chatHandler)
    http.HandleFunc("/chatlog", chatLogHandler)

    ips := getAllUsableIPs()
    fmt.Println("请用手机扫码网页上的二维码，或用以下任意地址访问：")
    for _, ip := range ips {
        fmt.Printf("  http://%s\n", ip)
    }
    log.Fatal(http.ListenAndServe("0.0.0.0:80", nil))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    ips := getAllUsableIPs()
    url := fmt.Sprintf("http://%s", ips[0])
    png, _ := qrcode.Encode(url, qrcode.Medium, 200)
    b64 := base64.StdEncoding.EncodeToString(png)

    var urlListHTML strings.Builder
    urlListHTML.WriteString(`<div style="font-size:15px;word-break:break-all;">`)
    for _, ip := range ips {
        urlListHTML.WriteString(fmt.Sprintf(`<div><a href="http://%s" target="_blank">http://%s</a></div>`, ip, ip))
    }
    urlListHTML.WriteString(`</div>`)

    html := `
    <!DOCTYPE html>
    <html>
    <head>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <meta charset="utf-8">
    <title>手机传文件/聊天到电脑</title>
    <style>
        html, body { margin: 0; padding: 0; height: 100%; background: #f3f3f3;
            font-family: "微软雅黑", Arial, sans-serif; font-size: 17px; box-sizing: border-box; }
        .container { max-width: 500px; margin: 0 auto; background: #fff; min-height: 100vh;
            padding: 0 0 18px 0; box-shadow: 0 4px 16px #0001; display: flex; flex-direction: column; }
        .qrcode-float { position: fixed; top: 18px; right: 18px; background: #fff; padding: 10px 10px 6px 10px;
            border: 1px solid #eee; border-radius: 10px; box-shadow: 0 2px 8px #0002; z-index: 99; text-align: center; }
        .qrcode-float img { width: 120px; max-width: 30vw; min-width: 60px; }
        .qrcode-url { font-size: 0.98em; word-break: break-all; margin: 7px 0 0 0; }
        h2, h3 { font-size: 1.17em; margin-bottom: 8px; margin-top: 18px; }
        .upload-form, .chatbox { width: 95%; max-width: 420px; margin: 0 auto 18px auto; }
        .upload-form input[type="file"] { font-size: 1em; }
        .upload-form input[type="submit"] { font-size: 1em; padding: 0.3em 2em; margin-left: 10px; }
        .chatbox { background: #fafbfc; border-radius: 6px; padding: 12px 2vw; box-shadow: 0 2px 8px #0001; min-width: 0; }
        .chatlog { height: 130px; max-height: 30vh; overflow-y: auto; background: #f5f5ff; padding: 8px;
            margin-bottom: 10px; font-size: 1em; border-radius: 4px; border: 1px solid #ececec; word-break: break-all; }
        .chat-form textarea { width: 97%; height: 38px; font-size: 1em; resize: vertical; }
        .chat-form button { margin-top: 6px; font-size: 1em; padding: 0.35em 1.6em; }
        .chat-msg-wrap { display: flex; align-items: center; margin-bottom: 7px; }
        .chat-msg-content { flex: 1; word-break: break-all; font-size: 1em; }
        .copy-btn { margin-left: 8px; padding: 0 10px; height: 24px; font-size: 13px; border: 1px solid #ccc;
            background: #f4f4f4; border-radius: 3px; cursor: pointer; }
        .copy-btn:active { background: #e0e0e0; }
        .time { color: #999; font-size: 12px; margin-right: 4px; }
        @media (max-width:600px) {
            html, body { font-size: 15px; }
            .container { max-width: 100vw; min-height: 100vh; box-shadow: none;}
            .qrcode-float { top:10px; right:10px; padding:6px;}
            .qrcode-float img { width:28vw; max-width:90px; min-width:40px;}
            .upload-form, .chatbox { width: 99vw; max-width: 99vw; }
            .chatlog { height: 80px; font-size: 0.98em; }
        }
        @media (max-width:370px) {
            .qrcode-float img { width: 38vw; max-width:60px; min-width:28px;}
        }
    </style>
    <script>
    function loadChat(){
        fetch('/chatlog')
        .then(r=>r.text())
        .then(txt=>{
            let chatlog = document.getElementById('chatlog');
            chatlog.innerHTML = txt;
            addCopyEvents();
            chatlog.scrollTop = chatlog.scrollHeight;
        });
    }
    function addCopyEvents() {
        document.querySelectorAll('.copy-btn').forEach(function(btn){
            btn.onclick = function(){
                let msg = this.getAttribute('data-msg');
                if (navigator.clipboard) {
                    navigator.clipboard.writeText(msg);
                } else {
                    let textarea = document.createElement('textarea');
                    textarea.value = msg;
                    document.body.appendChild(textarea);
                    textarea.select();
                    document.execCommand('copy');
                    document.body.removeChild(textarea);
                }
                this.innerText = '已复制';
                setTimeout(()=>{this.innerText='复制'}, 1000);
            }
        });
    }
    setInterval(loadChat, 3000);
    window.onload = function() {
        loadChat();
        // 聊天输入区回车快捷发送（shift+enter换行）
        var textarea = document.querySelector('.chat-form textarea');
        var form = document.querySelector('.chat-form');
        textarea.addEventListener('keydown', function(e) {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                form.querySelector('button[type="submit"]').click();
            }
        });
    }
    </script>
    </head>
    <body>
    <div class="qrcode-float">
        <div><b>扫码访问</b></div>
        <img src="data:image/png;base64,` + b64 + `" alt="二维码"/><br>
        <div class="qrcode-url"><a href="` + url + `">` + url + `</a></div>
        <div style="margin-top:10px;"><b>本机可用地址：</b></div>
        ` + urlListHTML.String() + `
    </div>
    <div class="container">
        <div class="upload-form">
            <h2>手机上传文件到电脑</h2>
            <form enctype="multipart/form-data" action="/upload" method="post">
                <input type="file" name="file" required />
                <input type="submit" value="上传"/>
            </form>
            <div style="color:#e60; margin-top:5px;">单个文件最大支持1GB</div>
        </div>
        <div class="chatbox">
            <h3>手机&电脑即时聊天</h3>
            <div class="chatlog" id="chatlog"></div>
            <form class="chat-form" action="/chat" method="post" onsubmit="setTimeout(loadChat, 200);">
                <textarea name="msg" required maxlength="200" placeholder="输入消息...（回车发送，Shift+回车换行）"></textarea><br/>
                <button type="submit">发送</button>
            </form>
        </div>
    </div>
    </body>
    </html>
    `
    w.Write([]byte(html))
}

func uploadHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        http.Redirect(w, r, "/", http.StatusSeeOther)
        return
    }
    // 限制最大1GB
    r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)

    file, handler, err := r.FormFile("file")
    if err != nil {
        http.Error(w, "上传失败，可能文件过大或服务异常", http.StatusBadRequest)
        return
    }
    defer file.Close()

    f, err := os.OpenFile(handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
    if err != nil {
        http.Error(w, "保存文件失败", http.StatusInternalServerError)
        return
    }
    defer f.Close()
    _, err = io.Copy(f, file)
    if err != nil {
        http.Error(w, "写入文件失败", http.StatusInternalServerError)
        return
    }
    http.Redirect(w, r, "/", http.StatusSeeOther)
}

func chatHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        http.Redirect(w, r, "/", http.StatusSeeOther)
        return
    }
    msg := r.FormValue("msg")
    msg = strings.TrimSpace(msg)
    if msg == "" || len(msg) > 200 {
        http.Redirect(w, r, "/", http.StatusSeeOther)
        return
    }
    ts := time.Now().Format("15:04:05")
    line := fmt.Sprintf("[%s] %s", ts, msg)
    if len(chatMemory) > 100 {
        chatMemory = chatMemory[1:]
    }
    chatMemory = append(chatMemory, line)
    http.Redirect(w, r, "/", http.StatusSeeOther)
}

func chatLogHandler(w http.ResponseWriter, r *http.Request) {
    var out strings.Builder
    for _, line := range chatMemory {
        if line = strings.TrimSpace(line); line != "" {
            safe := strings.ReplaceAll(line, "<", "&lt;")
            idx := strings.Index(safe, "]")
            timeStr := ""
            msgStr := safe
            if idx > 0 {
                timeStr = safe[:idx+1]
                msgStr = strings.TrimSpace(safe[idx+1:])
            }
            msgStr = strings.ReplaceAll(msgStr, "\"", "&quot;")
            msgStr = strings.ReplaceAll(msgStr, "'", "&#39;")
            out.WriteString(`<div class="chat-msg-wrap">`)
            out.WriteString(`<span class="time">` + timeStr + `</span>`)
            out.WriteString(`<span class="chat-msg-content">` + msgStr + `</span>`)
            out.WriteString(`<button class="copy-btn" data-msg="` + msgStr + `">复制</button>`)
            out.WriteString(`</div>`)
        }
    }
    w.Write([]byte(out.String()))
}